
// #include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_test.h>
#include <gsl/gsl_ieee_utils.h>

#define N 100000

/* Convient test dimension for multivariant distributions */
#define MULTI_DIM 10


void testMoments (double (*f) (void), const char *name,
                  double a, double b, double p);
void testPDF (double (*f) (void), double (*pdf) (double), const char *name);
void testDiscretePDF (double (*f) (void), double (*pdf) (unsigned int),
                      const char *name);

void test_shuffle (void);
void test_choose (void);
double test_beta (void);
double test_beta_pdf (double x);
double test_bernoulli (void);
double test_bernoulli_pdf (unsigned int n);

double test_binomial (void);
double test_binomial_pdf (unsigned int n);
double test_binomial_large (void);
double test_binomial_large_pdf (unsigned int n);
double test_binomial_huge (void);
double test_binomial_huge_pdf (unsigned int n);
double test_binomial0 (void);
double test_binomial0_pdf (unsigned int n);
double test_binomial1 (void);
double test_binomial1_pdf (unsigned int n);


double test_binomial_knuth (void);
double test_binomial_knuth_pdf (unsigned int n);
double test_binomial_large_knuth (void);
double test_binomial_large_knuth_pdf (unsigned int n);
double test_binomial_huge_knuth (void);
double test_binomial_huge_knuth_pdf (unsigned int n);

double test_gaussian (void);

gsl_rng *r_global;

static gsl_ran_discrete_t *g1 = NULL;
static gsl_ran_discrete_t *g2 = NULL;
static gsl_ran_discrete_t *g3 = NULL;

int
main (void)
{
  gsl_ieee_env_setup ();

  gsl_rng_env_setup ();
  r_global = gsl_rng_alloc (gsl_rng_default);

#define FUNC(x)  test_ ## x,                     "test gsl_ran_" #x
#define FUNC2(x) test_ ## x, test_ ## x ## _pdf, "test gsl_ran_" #x

  test_shuffle ();
  test_choose ();

  // testPDF (FUNC2 (gaussian));

  gsl_rng_free (r_global);
  gsl_ran_discrete_free (g1);
  gsl_ran_discrete_free (g2);
  gsl_ran_discrete_free (g3);

  exit (gsl_test_summary ());
}

void
test_shuffle (void)
{
  double count[10][10];
  int x[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  int i, j, status = 0;

  for (i = 0; i < 10; i++)
    {
      for (j = 0; j < 10; j++)
        {
          count[i][j] = 0;
        }
    }

  for (i = 0; i < N; i++)
    {
      for (j = 0; j < 10; j++)
        x[j] = j;

      gsl_ran_shuffle (r_global, x, 10, sizeof (int));

      for (j = 0; j < 10; j++)
        count[x[j]][j]++;
    }

  for (i = 0; i < 10; i++)
    {
      for (j = 0; j < 10; j++)
        {
          double expected = N / 10.0;
          double d = fabs (count[i][j] - expected);
          double sigma = d / sqrt (expected);
          if (sigma > 5 && d > 1)
            {
              status = 1;
              gsl_test (status,
                        "gsl_ran_shuffle %d,%d (%g observed vs %g expected)",
                        i, j, count[i][j] / N, 0.1);
            }
        }
    }

  gsl_test (status, "gsl_ran_shuffle on {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}");

}

void
test_choose (void)
{
  double count[10];
  int x[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  int y[3] = { 0, 1, 2 };
  int i, j, status = 0;

  for (i = 0; i < 10; i++)
    {
      count[i] = 0;
    }

  for (i = 0; i < N; i++)
    {
      for (j = 0; j < 10; j++)
        x[j] = j;

      gsl_ran_choose (r_global, y, 3, x, 10, sizeof (int));

      for (j = 0; j < 3; j++)
        count[y[j]]++;
    }

  for (i = 0; i < 10; i++)
    {
      double expected = 3.0 * N / 10.0;
      double d = fabs (count[i] - expected);
      double sigma = d / sqrt (expected);
      if (sigma > 5 && d > 1)
        {
          status = 1;
          gsl_test (status,
                    "gsl_ran_choose %d (%g observed vs %g expected)",
                    i, count[i] / N, 0.1);
        }
    }

  gsl_test (status, "gsl_ran_choose (3) on {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}");

}




void
testMoments (double (*f) (void), const char *name,
             double a, double b, double p)
{
  int i;
  double count = 0, expected, sigma;
  int status;

  for (i = 0; i < N; i++)
    {
      double r = f ();
      if (r < b && r > a)
        count++;
    }

  expected = p * N;
  sigma = fabs (count - expected) / sqrt (expected);

  status = (sigma > 3);

  gsl_test (status, "%s [%g,%g] (%g observed vs %g expected)",
            name, a, b, count / N, p);
}

#define BINS 100

void
testPDF (double (*f) (void), double (*pdf) (double), const char *name)
{
  double count[BINS], p[BINS];
  double a = -5.0, b = +5.0;
  double dx = (b - a) / BINS;
  int i, j, status = 0, status_i = 0;

  for (i = 0; i < BINS; i++)
    count[i] = 0;

  for (i = 0; i < N; i++)
    {
      double r = f ();
      if (r < b && r > a)
        {
          j = (int) ((r - a) / dx);
          count[j]++;
        }
    }

  for (i = 0; i < BINS; i++)
    {
      /* Compute an approximation to the integral of p(x) from x to
         x+dx using Simpson's rule */

      double x = a + i * dx;
#define STEPS 100
      double sum = 0;

      if (fabs (x) < 1e-10)     /* hit the origin exactly */
        x = 0.0;

      for (j = 1; j < STEPS; j++)
        sum += pdf (x + j * dx / STEPS);

      p[i] = 0.5 * (pdf (x) + 2 * sum + pdf (x + dx - 1e-7)) * dx / STEPS;
    }

  for (i = 0; i < BINS; i++)
    {
      double x = a + i * dx;
      double d = fabs (count[i] - N * p[i]);
      if (p[i] != 0)
        {
          double s = d / sqrt (N * p[i]);
          status_i = (s > 5) && (d > 1);
        }
      else
        {
          status_i = (count[i] != 0);
        }
      status |= status_i;
      if (status_i)
        gsl_test (status_i, "%s [%g,%g) (%g/%d=%g observed vs %g expected)",
                  name, x, x + dx, count[i], N, count[i] / N, p[i]);
    }

  if (status == 0)
    gsl_test (status, "%s, sampling against pdf over range [%g,%g) ",
              name, a, b);
}

void
testDiscretePDF (double (*f) (void), double (*pdf) (unsigned int),
                 const char *name)
{
  double count[BINS], p[BINS];
  unsigned int i;
  int status = 0, status_i = 0;

  for (i = 0; i < BINS; i++)
    count[i] = 0;

  for (i = 0; i < N; i++)
    {
      int r = (int) (f ());
      if (r >= 0 && r < BINS)
        count[r]++;
    }

  for (i = 0; i < BINS; i++)
    p[i] = pdf (i);

  for (i = 0; i < BINS; i++)
    {
      double d = fabs (count[i] - N * p[i]);
      if (p[i] != 0)
        {
          double s = d / sqrt (N * p[i]);
          status_i = (s > 5) && (d > 1);
        }
      else
        {
          status_i = (count[i] != 0);
        }
      status |= status_i;
      if (status_i)
        gsl_test (status_i, "%s i=%d (%g observed vs %g expected)",
                  name, i, count[i] / N, p[i]);
    }

  if (status == 0)
    gsl_test (status, "%s, sampling against pdf over range [%d,%d) ",
              name, 0, BINS);
}


double
test_gaussian (void)
{
  return gsl_ran_gaussian (r_global, 3.0);
}
